package com.bkhech.spring.security.example.mvc;

import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.core.Conventions;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.ValidationAnnotationUtils;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Map;

/**
 * 请求名转驼峰
 *
 * @author guowm
 * @date 2023/2/3
 */
@Component
public class RequestKey2CamelCaseMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @SuppressWarnings("FieldMayBeFinal")
    private static ObjectMapper objectMapper = new ObjectMapper();

    static {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 解决 LocalDateTime 的序列化
        objectMapper.registerModule(new JavaTimeModule());
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestKey2CamelCase.class);
    }

    @Override
    public Object resolveArgument(@NonNull MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  @NonNull NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {
        parameter = parameter.nestedIfOptional();
        final Type paramType = parameter.getNestedGenericParameterType();

        Object arg = read(webRequest, parameter, paramType);
        String name = Conventions.getVariableNameForParameter(parameter);

        // 参数校验
        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
            if (arg != null) {
                validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
                }
            }
            if (mavContainer != null) {
                mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
            }
        }

        return arg;
    }

    private <T> Object read(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws JsonProcessingException {
        final JavaType javaType = getJavaType(paramType, null);
        Map<String, String[]> parameterMap = webRequest.getParameterMap();
        Map<String, String> resultMap = CollectionUtils.newLinkedHashMap(parameterMap.size());
        parameterMap.forEach((key, values) -> {
            if (values.length > 0) {
                resultMap.put(key, values[0]);
            }
        });
        final String jsonStr = JSONUtil.toJsonStr(resultMap);
        System.out.println(jsonStr);
        final Object result = objectMapper.readValue(jsonStr, javaType);
        System.out.println("jsonStr: " + jsonStr + ", result: " + result);
        return result;
    }

    /**
     * 类型转换
     * 从 {@link Type} 包装为 {@link JavaType}
     *
     * @param type
     * @param contextClass
     * @return
     */
    protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
        return objectMapper.constructType(GenericTypeResolver.resolveType(type, contextClass));
    }

    /**
     * 如果适用，验证绑定目标。
     * 默认实现检查@javax.validation.Valid 、Spring 的org.springframework.validation.annotation.Validated和名称以“Valid”开头的自定义注释。
     *
     * @param binder    要使用的 DataBinder
     * @param parameter 方法参数描述符
     */
    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
        Annotation[] annotations = parameter.getParameterAnnotations();
        for (Annotation ann : annotations) {
            Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
            if (validationHints != null) {
                binder.validate(validationHints);
                break;
            }
        }
    }

    /**
     * 是否在验证错误时引发致命绑定异常。
     *
     * @param binder    用于执行数据绑定的数据绑定器
     * @param parameter 方法参数描述符
     * @return
     */
    protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
        int i = parameter.getParameterIndex();
        Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();
        boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
        return !hasBindingResult;
    }

}
